 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.registry;

 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Collections ;
 import java.util.Comparator ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.Set ;
 import java.util.StringTokenizer ;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtensionRegistry;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.ui.internal.WorkbenchMessages;
 import org.eclipse.ui.internal.WorkbenchPlugin;
 import org.eclipse.ui.internal.dialogs.WizardCollectionElement;
 import org.eclipse.ui.internal.dialogs.WorkbenchWizardElement;
 import org.eclipse.ui.internal.util.Util;

 import com.ibm.icu.text.Collator;

 /**
  * Instances access the registry that is provided at creation time
  * in order to determine the contained Wizards
  */
 public class WizardsRegistryReader extends RegistryReader {

     private String pluginPoint;
     
     private WizardCollectionElement wizardElements = null;

     private ArrayList deferWizards = null;

     private ArrayList deferCategories = null;

     private Set deferPrimary;

     // constants
 /**
      * Examples wizard category id
      */
     public final static String FULL_EXAMPLES_WIZARD_CATEGORY = "org.eclipse.ui.Examples";//$NON-NLS-1$
 /**
      * Other wizard category id
      */
     final public static String UNCATEGORIZED_WIZARD_CATEGORY = "org.eclipse.ui.Other";//$NON-NLS-1$
 /**
      * General wizard category id
      */
     final public static String GENERAL_WIZARD_CATEGORY = "org.eclipse.ui.Basic"; //$NON-NLS-1$

     final private static String UNCATEGORIZED_WIZARD_CATEGORY_LABEL = WorkbenchMessages.NewWizardsRegistryReader_otherCategory;
     
     private final static String CATEGORY_SEPARATOR = "/";//$NON-NLS-1$

     private WorkbenchWizardElement[] primaryWizards = new WorkbenchWizardElement[0];
     
     private class CategoryNode {
         private Category category;

         private String path;

         CategoryNode(Category cat) {
             category = cat;
             path = ""; //$NON-NLS-1$
 String [] categoryPath = category.getParentPath();
             if (categoryPath != null) {
                 for (int nX = 0; nX < categoryPath.length; nX++) {
                     path += categoryPath[nX] + '/';
                 }
             }
             path += cat.getId();
         }

         String getPath() {
             return path;
         }

         Category getCategory() {
             return category;
         }
     }

     private static final Comparator comparer = new Comparator () {
         private Collator collator = Collator.getInstance();

         public int compare(Object arg0, Object arg1) {
             String s1 = ((CategoryNode) arg0).getPath();
             String s2 = ((CategoryNode) arg1).getPath();
             return collator.compare(s1, s2);
         }
     };

     private boolean readAll = true;

     private String plugin;

     /**
      *Create an instance of this class.
      *
      * @param pluginId the plugin id
      * @param pluginPointId java.lang.String
      */
     public WizardsRegistryReader(String pluginId, String pluginPointId) {
         pluginPoint = pluginPointId;
         plugin = pluginId;
     }

     /* (non-Javadoc)
      * Method declared on WizardRegistryReader.
      * <p>
      * This implementation uses a defering strategy. For more info see
      * <code>readWizards</code>.
      * </p>
      */
     protected void addNewElementToResult(WorkbenchWizardElement element,
             IConfigurationElement config) {
         // TODO: can we remove the config parameter?
 deferWizard(element);
     }

     /**
      *
      * @param parent
      * @param element
      * @since 3.1
      */
     private WizardCollectionElement createCollectionElement(WizardCollectionElement parent, IConfigurationElement element) {
         WizardCollectionElement newElement = new WizardCollectionElement(
                 element, parent);

         parent.add(newElement);
         return newElement;
     }
     /**
      * Create and answer a new WizardCollectionElement, configured as a
      * child of <code>parent</code>
      *
      * @return org.eclipse.ui.internal.model.WizardCollectionElement
      * @param parent org.eclipse.ui.internal.model.WizardCollectionElement
      * @param id the id of the new collection
      * @param pluginId the originating plugin id of the collection, if any. <code>null</code> otherwise.
      * @param label java.lang.String
      */
     protected WizardCollectionElement createCollectionElement(
             WizardCollectionElement parent, String id, String pluginId,
             String label) {
         WizardCollectionElement newElement = new WizardCollectionElement(id,
                 pluginId, label, parent);

         parent.add(newElement);
         return newElement;
     }

     /**
      * Creates empty element collection. Overrider to fill
      * initial elements, if needed.
      */
     protected void createEmptyWizardCollection() {
         wizardElements = new WizardCollectionElement("root", null, "root", null);//$NON-NLS-2$//$NON-NLS-1$
 }
     
     /**
      * Set the initial wizard set for supplemental reading via dynamic plugin loading.
      *
      * @param wizards the wizards
      * @since 3.1
      */
     public void setInitialCollection(WizardCollectionElement wizards) {
         wizardElements = wizards;
         readAll = false;
     }

     /**
      * Stores a category element for deferred addition.
      */
     private void deferCategory(IConfigurationElement config) {
         // Create category.
 Category category = null;
         try {
             category = new Category(config);
         } catch (CoreException e) {
             WorkbenchPlugin.log("Cannot create category: ", e.getStatus());//$NON-NLS-1$
 return;
         }

         // Defer for later processing.
 if (deferCategories == null) {
             deferCategories = new ArrayList (20);
         }
         deferCategories.add(category);
     }


     /**
      * Stores a wizard element for deferred addition.
      */
     private void deferWizard(WorkbenchWizardElement element) {
         if (deferWizards == null) {
             deferWizards = new ArrayList (50);
         }
         deferWizards.add(element);
     }

     /**
      * Finishes the addition of categories. The categories are sorted and
      * added in a root to depth traversal.
      */
     private void finishCategories() {
         // If no categories just return.
 if (deferCategories == null) {
             return;
         }

         // Sort categories by flattened name.
 CategoryNode[] flatArray = new CategoryNode[deferCategories.size()];
         for (int i = 0; i < deferCategories.size(); i++) {
             flatArray[i] = new CategoryNode((Category) deferCategories.get(i));
         }
         Collections.sort(Arrays.asList(flatArray), comparer);

         // Add each category.
 for (int nX = 0; nX < flatArray.length; nX++) {
             Category cat = flatArray[nX].getCategory();
             finishCategory(cat);
         }

         // Cleanup.
 deferCategories = null;
     }

     /**
      * Save new category definition.
      */
     private void finishCategory(Category category) {
         String [] categoryPath = category.getParentPath();
         WizardCollectionElement parent = wizardElements; // ie.- root

         // Traverse down into parent category.
 if (categoryPath != null) {
             for (int i = 0; i < categoryPath.length; i++) {
                 WizardCollectionElement tempElement = getChildWithID(parent,
                         categoryPath[i]);
                 if (tempElement == null) {
                     // The parent category is invalid. By returning here the
 // category will be dropped and any wizard within the category
 // will be added to the "Other" category.
 return;
                 }
                 parent = tempElement;
             }
         }

         // If another category already exists with the same id ignore this one.
 Object test = getChildWithID(parent, category.getId());
         if (test != null) {
             return;
         }

         if (parent != null) {
             createCollectionElement(parent, (IConfigurationElement) Util.getAdapter(category,
                     IConfigurationElement.class));
         }
     }


     /**
      * Finishes the recognition of primary wizards.
      */
     private void finishPrimary() {
         if (deferPrimary != null) {
             ArrayList primary = new ArrayList ();
             for (Iterator i = deferPrimary.iterator(); i.hasNext();) {
                 String id = (String ) i.next();
                 WorkbenchWizardElement element = getWizardElements()
                         .findWizard(id, true);
                 if (element != null) {
                     primary.add(element);
                 }
             }

             primaryWizards = (WorkbenchWizardElement[]) primary
                     .toArray(new WorkbenchWizardElement[primary.size()]);

             deferPrimary = null;
         }
     }


     /**
      * Insert the passed wizard element into the wizard collection appropriately
      * based upon its defining extension's CATEGORY tag value
      *
      * @param element WorkbenchWizardElement
      * @param config configuration element
      */
     private void finishWizard(WorkbenchWizardElement element,
             IConfigurationElement config) {
         StringTokenizer familyTokenizer = new StringTokenizer (
                 getCategoryStringFor(config), CATEGORY_SEPARATOR);

         // use the period-separated sections of the current Wizard's category
 // to traverse through the NamedSolution "tree" that was previously created
 WizardCollectionElement currentCollectionElement = wizardElements; // ie.- root
 boolean moveToOther = false;

         while (familyTokenizer.hasMoreElements()) {
             WizardCollectionElement tempCollectionElement = getChildWithID(
                     currentCollectionElement, familyTokenizer.nextToken());

             if (tempCollectionElement == null) { // can't find the path; bump it to uncategorized
 moveToOther = true;
                 break;
             }
             currentCollectionElement = tempCollectionElement;
         }

         if (moveToOther) {
             moveElementToUncategorizedCategory(wizardElements, element);
         } else {
             currentCollectionElement.add(element);
             element.setParent(currentCollectionElement);
         }
     }

     /**
      * Finishes the addition of wizards. The wizards are processed and categorized.
      */
     private void finishWizards() {
         if (deferWizards != null) {
             Iterator iter = deferWizards.iterator();
             while (iter.hasNext()) {
                 WorkbenchWizardElement wizard = (WorkbenchWizardElement) iter
                         .next();
                 IConfigurationElement config = wizard.getConfigurationElement();
                 finishWizard(wizard, config);
             }
             deferWizards = null;
         }
     }

     /**
      * Return the appropriate category (tree location) for this Wizard.
      * If a category is not specified then return a default one.
      */
     protected String getCategoryStringFor(IConfigurationElement config) {
         String result = config.getAttribute(IWorkbenchRegistryConstants.TAG_CATEGORY);
         if (result == null) {
             result = UNCATEGORIZED_WIZARD_CATEGORY;
         }

         return result;
     }

     /**
      * Go through the children of the passed parent and answer the child
      * with the passed name. If no such child is found then return null.
      *
      * @return org.eclipse.ui.internal.model.WizardCollectionElement
      * @param parent org.eclipse.ui.internal.model.WizardCollectionElement
      * @param id java.lang.String
      */
     protected WizardCollectionElement getChildWithID(
             WizardCollectionElement parent, String id) {
         Object [] children = parent.getChildren(null);
         for (int i = 0; i < children.length; ++i) {
             WizardCollectionElement currentChild = (WizardCollectionElement) children[i];
             if (currentChild.getId().equals(id)) {
                 return currentChild;
             }
         }
         return null;
     }

     /**
      * Moves given element to "Other" category, previously creating one if missing.
      */
     protected void moveElementToUncategorizedCategory(
             WizardCollectionElement root, WorkbenchWizardElement element) {
         WizardCollectionElement otherCategory = getChildWithID(root,
                 UNCATEGORIZED_WIZARD_CATEGORY);

         if (otherCategory == null) {
             otherCategory = createCollectionElement(root,
                     UNCATEGORIZED_WIZARD_CATEGORY, null,
                     UNCATEGORIZED_WIZARD_CATEGORY_LABEL);
         }

         otherCategory.add(element);
         element.setParent(otherCategory);
     }

     /**
      * Removes the empty categories from a wizard collection.
      */
     private void pruneEmptyCategories(WizardCollectionElement parent) {
         Object [] children = parent.getChildren(null);
         for (int nX = 0; nX < children.length; nX++) {
             WizardCollectionElement child = (WizardCollectionElement) children[nX];
             pruneEmptyCategories(child);
             boolean shouldPrune = child.getId().equals(FULL_EXAMPLES_WIZARD_CATEGORY);
             if (child.isEmpty() && shouldPrune) {
                 parent.remove(child);
             }
         }
     }

     /**
      * Implement this method to read element attributes.
      */
     public boolean readElement(IConfigurationElement element) {
         if (element.getName().equals(IWorkbenchRegistryConstants.TAG_CATEGORY)) {
             deferCategory(element);
             return true;
         } else if (element.getName().equals(IWorkbenchRegistryConstants.TAG_PRIMARYWIZARD)) {
             if (deferPrimary == null) {
                 deferPrimary = new HashSet ();
             }
             deferPrimary.add(element.getAttribute(IWorkbenchRegistryConstants.ATT_ID));

             return true;
         } else {
             if (!element.getName().equals(IWorkbenchRegistryConstants.TAG_WIZARD)) {
                 return false;
             }
             WorkbenchWizardElement wizard = createWizardElement(element);
             if (wizard != null) {
                 addNewElementToResult(wizard, element);
             }
             return true;
         }
     }

     /**
      * Reads the wizards in a registry.
      * <p>
      * This implementation uses a defering strategy. All of the elements
      * (categories, wizards) are read. The categories are created as the read occurs.
      * The wizards are just stored for later addition after the read completes.
      * This ensures that wizard categorization is performed after all categories
      * have been read.
      * </p>
      */
     protected void readWizards() {
         if (readAll) {
                if (!areWizardsRead()) {
                 createEmptyWizardCollection();
                 IExtensionRegistry registry = Platform.getExtensionRegistry();
                 readRegistry(registry, plugin, pluginPoint);
             }
         }
         finishCategories();
         finishWizards();
         finishPrimary();
         if (wizardElements != null) {
             pruneEmptyCategories(wizardElements);
         }
     }

     /**
      * Returns the list of wizards that are considered 'primary'.
      *
      * The return value for this method is cached since computing its value
      * requires non-trivial work.
      *
      * @return the primary wizards
      */
     public WorkbenchWizardElement [] getPrimaryWizards() {
         if (!areWizardsRead()) {
             readWizards();
         }
         return primaryWizards;
     }


     /**
      * Returns whether the wizards have been read already
      */
     protected boolean areWizardsRead() {
         return wizardElements != null && readAll;
     }

     /**
      * Returns a list of wizards, project and not.
      *
      * The return value for this method is cached since computing its value
      * requires non-trivial work.
      *
      * @return the wizard collection
      */
     public WizardCollectionElement getWizardElements() {
         if (!areWizardsRead()) {
             readWizards();
         }
         return wizardElements;
     }

     protected Object [] getWizardCollectionElements() {
         if (!areWizardsRead()) {
             readWizards();
         }
         return wizardElements.getChildren();
     }
     
     /**
      * Returns a new WorkbenchWizardElement configured according to the parameters
      * contained in the passed Registry.
      *
      * May answer null if there was not enough information in the Extension to create
      * an adequate wizard
      */
     protected WorkbenchWizardElement createWizardElement(
             IConfigurationElement element) {
         // WizardElements must have a name attribute
 if (element.getAttribute(IWorkbenchRegistryConstants.ATT_NAME) == null) {
             logMissingAttribute(element, IWorkbenchRegistryConstants.ATT_NAME);
             return null;
         }
         
         if (getClassValue(element, IWorkbenchRegistryConstants.ATT_CLASS) == null) {
             logMissingAttribute(element, IWorkbenchRegistryConstants.ATT_CLASS);
             return null;
         }
         return new WorkbenchWizardElement(element);
     }

     /**
      * Returns the first wizard with a given id.
      *
      * @param id wizard id to search for
      * @return WorkbenchWizardElement matching the given id, if found; null otherwise
      */
     public WorkbenchWizardElement findWizard(String id) {
         Object [] wizards = getWizardCollectionElements();
         for (int nX = 0; nX < wizards.length; nX++) {
             WizardCollectionElement collection = (WizardCollectionElement) wizards[nX];
             WorkbenchWizardElement element = collection.findWizard(id, true);
             if (element != null) {
                 return element;
             }
         }
         return null;
     }
 }

